﻿using Red.Controls.Shell;
using Red.Core.Extensions;
using Red.EngineData.Protocol.Interop;
using Red.EngineData.View;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Red.Controls.ViewModels;

namespace Red.Toolkit.Console
{
    public partial class Console : RedEditorWindow
    {
        #region Data

        private const string _cmdPrefix = "-";

        private struct CommandEntry
        {
            public OnCommandHandler handler;
            public string help;
        }

        private delegate void OnCommandHandler( InteropDataItem root, string[] args );
        private readonly Dictionary<string, CommandEntry> _commands = new Dictionary<string, CommandEntry>();

        #endregion Data

        #region Private methods

        private void RegisterCommands()
        {
            RegisterCommand( "r", OnCommand_Root, "Change the current root. Start with /. to give an absolute path" );
            RegisterCommand( "b", OnCommand_Back, "Change the root to one level up" );
            RegisterCommand( "l", OnCommand_List, "List children of the current root" );
            RegisterCommand( "h", OnCommand_Help, "Show this list of commands" );
            RegisterCommand( "cls", OnCommand_Clear, "Clear console" );
            RegisterCommand( "q", OnCommand_Quit, "Quit console" );
            RegisterCommand( "f", OnCommand_Feed, "Feed the data grid with the current object" );
            RegisterCommand( "cs", OnCommand_CompileShaders, "Compile (static) shaders" );
            RegisterCommand( "stress", OnCommand_Stress, null );
        }

        private void RegisterCommand( string cmd, OnCommandHandler handler, string help )
        {
            _commands.Add( cmd, new CommandEntry() { handler = handler, help = help } );
        }

        private void ShowCommandsHelp()
        {
            foreach ( var cmd in _commands )
            {
                if ( !string.IsNullOrEmpty( cmd.Value.help ) )
                {
                    Term.AddLine( _cmdPrefix + cmd.Key + "\t" + cmd.Value.help );
                }
            }

            Term.ShowPrompt();
        }

        private void SetAvailableCommands( InteropResult result )
        {
            if ( result.InteropType == EInteropType.Compound )
            {
                Term.SetAvailableCommands( result.Children );
            }
        }

        #endregion Private methods

        #region Commands

        private void OnCommand_Stress( InteropDataItem root, string[] args )
        {
            long curTime = 0;
            var stopWatch = new Stopwatch();
            stopWatch.Start();
            for ( int i = 0; i < 1000; ++i )
            {
                long time = stopWatch.ElapsedMilliseconds;
                Term.AddLine( $"Stress test line {i}, time {time-curTime} ms" );
                curTime = time;
            }

            Term.ShowPrompt();
        }

        private void OnCommand_Root( InteropDataItem root, string[] args )
        {
            if ( args.Length > 0 )
            {
                bool absolute = args[0].StartsWith( "/" );
                string newPath = absolute ? args[0].Substring( 1 ) : DataView.CombinePath( _curPath, args[0] );

                if ( newPath.Length == 1 && newPath[0] == '.' ) // "$r /." is the same as "$r /"
                    newPath = string.Empty;

                if ( args[0] == ".." )
                {
                    var pos = _curPath.LastIndexOf( '.' );
                    if ( pos != -1 )
                        newPath = _curPath.Substring( 0, pos );
                    else
                        newPath = _curPath;
                }

                root.Data.Execute( Interop.Make( newPath ),
                    result =>
                    {
                        _curPath = newPath;
                        Breadcrumb.Path = GetBreadcrumbPath( _curPath );
                        ResetPrompt();
                        Term.ShowPrompt();
                        SetAvailableCommands( result );
                    },
                    ex =>
                    {
                        DisplayException( ex );
                    } );
            }
            else
            {
                Term.AddLine( $"Current root: [{GetDisplayPath( _curPath )}]" );
                Term.ShowPrompt();
            }
        }

        private void OnCommand_Back( InteropDataItem root, string[] args )
        {
            int lastBracketIdx = _curPath.LastIndexOf( '[' );
            int lastDotIdx = _curPath.LastIndexOf( '.' );
            int lastSepIdx = Math.Max( lastBracketIdx, lastDotIdx );

            if ( lastSepIdx != -1 )
                _curPath = _curPath.Substring( 0, lastSepIdx );
            else
                _curPath = "";

            Breadcrumb.Path = GetBreadcrumbPath( _curPath );
            ResetPrompt();
            Term.ShowPrompt();
        }

        private void OnCommand_List( InteropDataItem root, string[] args )
        {
            root.Data.Execute( Interop.Make( _curPath ),
                result =>
                {
                    DisplayResult( result );
                },
                ex =>
                {
                    DisplayException( ex );
                } );
        }

        private void OnCommand_Help( InteropDataItem root, string[] args )
        {
            ShowCommandsHelp();
        }

        private void OnCommand_Clear( InteropDataItem root, string[] args )
        {
            ResetTerminal();
        }

        private void OnCommand_Quit( InteropDataItem root, string[] args )
        {
            Term.ShowPrompt();
            Close();
        }

        private void OnCommand_Feed( InteropDataItem root, string[] args )
        {
            FeedToGrid( _curPath ).DoNotAwait();
            Term.ShowPrompt();
        }

        private void OnCommand_CompileShaders(InteropDataItem root, string[] args)
        {
            Action< object, DataReceivedEventArgs > dataReceived = ( sender, e ) =>
            {
                this.Dispatcher.Invoke( (Action)( ( ) =>
                {
                    Term.AddLine( "[staticShaderCompiler] " + e.Data, false );
                    Term.ShowPrompt();
                } ) );
            };

            string platform = "pc"; // 0 - pc, 1 - ps4, 2 - xb1

            if ( args.Length == 0 )
            {
                Term.AddLine("Specify 'pc', 'ps4', or 'xb1' to compile shaders for given platform.");
                Term.ShowPrompt();
                return;
            }
            else if ( args.Length == 1 )
            {
                if ( args[ 0 ] == "ps4" )
                {
                    platform = "orbis";
                }
                if ( args[ 0 ] == "xb1" )
                {
                    platform = "durango";
                }
            }
 
            var proc = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = Red.CommUtils.Application.AppUtils.engineBinPath + "staticShaderCompiler.exe",
                    Arguments = "-platform=" + platform + " -engineRoot=" + Red.CommUtils.Application.AppUtils.enginePath,
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    CreateNoWindow = true,
                }
            };

            proc.EnableRaisingEvents = true;
            proc.OutputDataReceived += new DataReceivedEventHandler(dataReceived);
            proc.ErrorDataReceived += new DataReceivedEventHandler(dataReceived);
            proc.Start();
            proc.BeginOutputReadLine();
            proc.BeginErrorReadLine();

            Term.ShowPrompt();
        }

        #endregion Commands
    }
}
